<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>7.3.3-自定义渲染器</title>
</head>

<body>
    <div id="app"></div>
</body>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        /**
         * 实现一个用来打印渲染器操作流程的自定义渲染器
         * - 在 createElement 内，我们不再调用浏览器的 API，
         *    而是仅仅返回一个对象 { tag }，并将其作为创建出来的 “DOM 元素”
         * - 在 setElementText 以及 insert 函数内，我们也没有调用浏览器相关的 API，
         *    而是自定义了一些逻辑，并打印信息到控制台
         * - 自定义渲染器不依赖浏览器特有的 API，
         *    所以这段代码不仅可以在浏览器中运行，还可以在 Node.js 中运行
         * 
         * 自定义渲染器并不是“黑魔法”，
         *  它只是通过抽象的手段，让核心代码不再依赖平台特有的 API，
         *  再通过支持个性化配置的能力来实现跨平台。
         * */ 
        
        function createRenderer(options) {
            // 通过 options 得到操作 DOM 的 API
            const {
                createElement,
                insert,
                setElementText
            } = options

            // 在这个作用域内定义的函数都可以访问那些 API

            function mountElement(vnode, container) {
                // 调用 createElement 函数创建元素
                const el = createElement(vnode.type)
                if (typeof vnode.children === 'string') {
                    // 调用 setElementText 设置元素的文本节点
                    setElementText(el, vnode.children)
                }
                // 调用 insert 函数将元素插入到容器内
                insert(el, container)
            }

            // 第一个参数 n1 代表旧 vnode，第二个参数 n2 代表新 vnode。
            function patch(n1, n2, container) {
                // 如果 n1 不存在，意味着挂载，则调用 mountElement 函数完成挂载
                if (!n1) {
                    mountElement(n2, container)
                } else {
                    // n1 存在，意味着打补丁，暂时省略
                }
            }

            function render(vnode, container) {
                if (vnode) {
                    // 新 vnode 存在，将其与旧 vnode 一起传递给 patch 函数，进行打补丁
                    patch(container._vnode, vnode, container)
                } else {
                    if (container._vnode) {
                        // 旧 vnode 存在，且新 vnode 不存在，说明是卸载（unmount）操作
                        // 只需要将 container 内的 DOM 清空即可
                        container.innerHTML = ''
                    }
                }
                // 把 vnode 存储到 container._vnode 下，即后续渲染中的旧 vnode
                container._vnode = vnode
            }

            return {
                render
            }
        }

        // 创建一个渲染器
        // 在创建 renderer 时传入配置项
        const renderer = createRenderer({  // 新增 console
            // 用于创建元素
            createElement(tag) {
                console.log(`创建元素 ${tag}`)
                return { tag }
            },
            // 用于设置元素的文本节点
            setElementText(el, text) {
                console.log(`设置 ${JSON.stringify(el)} 的文本内容：${text}`)
                el.textContent = text
            },
            // 用于在给定的 parent 下添加指定元素
            insert(el, parent, anchor = null) {
                console.log(`将 ${JSON.stringify(el)} 添加到 ${JSON.stringify(parent)} 下`)
                parent.children = el
            }
        })

        // 新增 测试
        // vnode 对象来描述一个 <h1> 标签
        const vnode = {
            type: 'h1',
            children: 'hello'
        }
        // 使用一个对象模拟挂载点
        const container = { type: 'root' }
        

        // 调用 render 函数渲染该 vnode
        renderer.render(vnode, container)




    </script>
    

</html>